﻿//===================================================================================
// Copyright (c) Ryan Garrett.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================

//WRITE: Write about the problems associated with syncing to an ephemoral model. For example, what happens when the model is removed
using System;

namespace Caffeine.Core
{
    /// <summary>
    /// Provides a default implementation for property syncing.
    /// </summary>
    /// <typeparam name="T">Type of property value to sync</typeparam>
    public class PropertySync<T> : IPropertySync<T>
    {
        #region State

        private readonly ModelScalarPropertyBase _parentProperty;
        private readonly WeakReference _syncedPropertyReference;
        private readonly ModelProperty<T> _syncedToProperty;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="parentProperty">Parent property of this sync object</param>
        /// <param name="syncedToProperty">Property to sync to. This will create a weak reference to the property.</param>
        public PropertySync(ModelScalarPropertyBase parentProperty, ModelProperty<T> syncedToProperty)
            : this(parentProperty, syncedToProperty, false)
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="parentProperty">Parent property of this sync object</param>
        /// <param name="syncedToProperty">Property to sync to. This will create a weak reference to the property.</param>
        /// <param name="updateOnCommit">Whether or not the property should be synced to when a value is committed</param>
        public PropertySync(ModelScalarPropertyBase parentProperty, ModelProperty<T> syncedToProperty, Boolean updateOnCommit)
            : this(parentProperty, syncedToProperty, updateOnCommit, false)
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="parentProperty">Parent property of this sync object</param>
        /// <param name="syncedToProperty">Property to sync to.</param>
        /// <param name="updateOnCommit">Whether or not the property should be synced to when a value is committed</param>
        /// <param name="holdConcreteReference">True to hold a concrete reference to the property, otherwise false. False is recommended.</param>
        public PropertySync
        (
            ModelScalarPropertyBase parentProperty,
            ModelProperty<T> syncedToProperty, 
            Boolean updateOnCommit, 
            Boolean holdConcreteReference
        )
        {
            if (parentProperty == null)
            {
                throw new ArgumentNullException("parentProperty");
            }

            if (syncedToProperty == null)
            {
                throw new ArgumentNullException("syncedToProperty");
            }

            if (ModelBaseExtensions.CheckIsValidModelSubType(syncedToProperty.PropertyType))
            {
                throw new ArgumentException("Cannot sync models", "syncedToProperty");
            }

            _parentProperty = parentProperty;
            ParentProperty.UpdateSyncOnCommit = updateOnCommit;

            if (holdConcreteReference)
            {
                _syncedToProperty = syncedToProperty;
            }
            else
            {
                _syncedPropertyReference = new WeakReference(syncedToProperty);
            }
        }

        #endregion

        #region Public

        /// <summary>
        /// Gets the owning property of this sync object
        /// </summary>
        public ModelScalarPropertyBase ParentProperty
        {
            get
            {
                return _parentProperty;
            }
        }

        /// <summary>
        /// Gets the property that will be synced to if a strong reference to the property is held
        /// </summary>
        public ModelProperty<T> SyncedToProperty
        {
            get
            {
                return _syncedToProperty;
            }
        }

        /// <summary>
        /// Gets the property that will be synced to if a weak reference to the property is held
        /// </summary>
        public WeakReference SyncedToPropertyReference
        {
            get
            {
                return _syncedPropertyReference;
            }
        }

        /// <summary>
        /// Called when a value is to be synced from one property to another
        /// </summary>
        /// <param name="value">Signal a value is ready to be synced</param>
        public virtual void SignalValueChange(T value)
        {
            if (SyncedToProperty != null)
            {
                if (SyncedToProperty.AllowSync)
                {
                    SyncedToProperty.Value = value;
                }
            }
            else
            {
                ModelProperty<T> property = SyncedToPropertyReference.Target as ModelProperty<T>;

                if (property != null && property.AllowSync)
                {
                    property.Value = value;
                }
            }
        }

        #endregion
    }
}
